<body>
  <div class="buttons">
    <button id="prime">Highlight Primes</button>
    <button id="even">Highlight Even</button>
    <button id="even2">Stable highlight Even</button>
    <button id="b">Clear</button>
    <button id="select-ids">Select ids</button>
    <input id="input-ids" title="selection" label="selection" type="text" value="1066 1968 2431 10012" />
    <button id="select-lots-of-ids">Select lots of ids</button>
    <div id="filter">FILTER:</div>
    <div id="filter2">FILTER2:</div>
    <div id="foreground">FOREGROUND:</div>
    <div id="categorical">CATEGORICAL:</div>
  </div>
  <textarea style="display:none" data-testid="API">
  
  </textarea>
  <div id="deepscatter"></div>
</body>
<script type="module" lang="ts">
  import { Scatterplot, Deeptable, Bitmask, dictionaryFromArrays } from './src/deepscatter';
  import {
    tableFromArrays,
    Table,
    RecordBatch,
    vectorFromArray,
    Utf8,
    Int8,
    Int16,
    Int32,
    Dictionary,
    makeVector
  } from 'apache-arrow';
  const num_batches = 4;
  window.RecordBatch = RecordBatch;
  window.vectorFromArray = vectorFromArray;
  function num_to_string(d) {
    return Number(d).toString();
  }
  let batch_no = 0;

  const queryString = window.location.search;
  const urlParams = new URLSearchParams(queryString);
  const filterMethod = urlParams.get('filter-method') || 'bitmask';

  // A function to create a single batch
  function createTable(n_batches) {
    function make_batch(start = 0, length = 65536) {
      const batch_number_here = batch_no++;

      let x = new Float32Array(length);
      let y = new Float32Array(length);
      let integers = new Int32Array(length);
      let ix = new Uint32Array(length);
      let batch_id = new Float32Array(length).fill(batch_number_here);
      for (let i = start; i < start + length; i++) {
        ix[i - start] = i;
        let x_ = 0;
        let y_ = 0;
        const binary = i.toString(2).split('').reverse();
        for (let j = 0; j < binary.length; j++) {
          const bit = binary[j];
          if (bit == 1) {
            if (j % 2 == 0) {
              x_ += 2 ** (j / 2);
            } else {
              y_ += 2 ** ((j - 1) / 2);
            }
          }
        }
        x[i - start] = x_;
        y[i - start] = y_;
        integers[i - start] = i;
      }

      const vs = [...ix].map(num_to_string);
      //      const _id = vectorFromArray(vs, new Utf8());
      //      console.log({ _id });
      return new Table({
        x: vectorFromArray(x),
        y: vectorFromArray(y),
        _id: vectorFromArray(vs, new Utf8()),
        integers: vectorFromArray(integers),
        batch_id: vectorFromArray(batch_id),
      });
    }
    const batches = [];
    const SIZE = 65536 / 4 / 4;
    for (let i = 0; i < n_batches; i++) {
      const batch = make_batch(i * SIZE, SIZE);
      batches.push(batch);
      window.b = batch;
    }
    const table = new Table([batches]);
    return table;
  }

  const table = createTable(num_batches);
  const plot = new Scatterplot('#deepscatter');

  function eratosthenes(n) {
    // improved from https://stackoverflow.com/questions/15471291/sieve-of-eratosthenes-algorithm-in-javascript-running-endless-for-large-number
    // Eratosthenes algorithm to find all primes under n
    var upperLimit = Math.sqrt(n),
      output = [2];

    // Make an array from 2 to (n - 1)
    const array = new Uint32Array(n);

    // Remove multiples of primes starting from 2, 3, 5,...
    for (var i = 3; i <= upperLimit; i += 2) {
      if (array[i] == 0) {
        for (var j = i * i; j < n; j += i * 2) array[j] = 1;
      }
    }

    // All array[i] set to 1 (true) are primes
    for (var i = 3; i < n; i += 2) {
      if (array[i] == 0) {
        output.push(i);
      }
    }

    return output;
  }

  const draw1 = plot.plotAPI({
    arrow_table: table,
    point_size: 2.5,
    max_points: num_batches * 65536,
    alpha: 25,
    background_color: '#EEEDDE',
    zoom_balance: 0.75,
    duration: 500,
    encoding: {
      x: {
        field: 'x',
        transform: 'literal',
      },
      y: {
        field: 'y',
        transform: 'literal',
      },
      color: {
        field: 'integers',
        range: 'viridis',
        domain: [1, 65000 / 4],
        transform: 'log',
      },
    },
  });

  draw1.then(() => {
    for (let dim of ['filter', 'filter2', 'foreground']) {
      const id = document.getElementById(dim);
      const button = document.createElement('button');
      button.textContent = `clear`;
      const encoding = {};
      encoding[dim] = null;

      button.addEventListener('click', function () {
        plot.plotAPI({ encoding });
      });
      id.appendChild(button);

      for (const i of [2, 3, 5, 7, 11, 13, 17]) {
        const button = document.createElement('button');
        button.textContent = `products of ${i}`;
        button.addEventListener('click', function () {
          bindproductsOf(i);
          const encoding = {};
          encoding[dim] = {
            field: `products of ${i}`,
            op: 'gt',
            a: 0,
          };
          plot.plotAPI({
            encoding,
          });
        });
        id.appendChild(button);
      }
    }
    const id = document.getElementById("categorical");
    const button = document.createElement('button');
    button.textContent = `Color by lowest prime factor`;

    plot.ready.then(() => {
      plot._root.promise.then(dataset => {
        [2, 3, 5, 7, 11, 13, 17, 19].map(prime => {
          if (plot.dataset.transformations[`products of ${prime}`] === undefined) {
            bindproductsOf(prime)
          }
        })
      })
    })
    button.addEventListener('click', function () {
      plot.dataset.transformations['lowest_prime'] = async function (tile) {
        const factors = {}
        const primes = [2, 3, 5, 7, 11, 13, 17];
        const labels = ["NA", "2", "3", "5", "7", "11", "13", "seventeen"]
        const indices = new Int8Array(tile.record_batch.numRows);
        const lookups = []
        for (const prime of primes) {
          lookups.push(await tile.get_column(`products of ${prime}`))
        }
        outer: for (let i = 0; i < tile.record_batch.numRows; i++) {
          for (let j = 0; j < lookups.length; j++) {
            if (i < 10) {
              //            console.log(i, j, lookups[j].get(i))
            }
            if (lookups[j].get(i) > 0) {
              indices[i] = j + 1
              continue outer
            }
          }
        }
        const dicto = dictionaryFromArrays(labels, indices)
        return dicto
      }
      plot.plotAPI({
        encoding: {
          color: {
            field: "lowest_prime",
            range: 'dark2'
          }
        }
      })
    })

    id.appendChild(button);

    {
      const id = document.getElementById("categorical");
      // colorize by factors
      const numbers = []
      for (let i = 0; i < 1_000_000; i++) {
        numbers.push("" + i)
      }

      let dictionaryBuilder = undefined
      const button = document.createElement('button');
      button.textContent = `Color by individual numbers as factors`;
      plot.ready.then(() => {
        if (dictionaryBuilder === undefined) {
          // Curry up the numbers first to ensure we're always in the same dictionary.
          dictionaryBuilder = dictionaryFromArrays(numbers);
        }
        button.addEventListener('click', function () {
          plot.dataset.transformations['dictionary number coloring'] = async function (tile) {
            const num = await tile.get_column("integers");
            return dictionaryBuilder(num.toArray())
          }
          plot.plotAPI({
            encoding: {
              color: {
                field: "dictionary number coloring",
                range: 'category10'
              }
            }
          })
        })
      })
      id.appendChild(button);
    }
  });
  window.plot = plot;
  const functions = {
    prime: (n) => eratosthenes(n),
    even: (n) => [...Array(n).keys()].filter((x) => x % 2 === 0),
    stable_even: (n) => [...Array(n).keys()].filter((x) => x % 2 === 0),
  };

  function bindproductsOf(n) {
    if (filterMethod === 'float32') {
      plot.dataset.transformations[`products of ${n}`] = function (tile) {
        const integers = tile.record_batch.getChild('integers');
        const output = new Float32Array(integers.length);
        for (let i = 0; i < integers.length; i += 1) {
          let int = integers.get(i);
          if (int % n === 0) {
            output[i] += 1;
            int = int / n;
          }
        }
        return output;
      };
    } else if (filterMethod === 'bitmask') {
      plot.dataset.transformations[`products of ${n}`] = async function (tile) {
        const integers = tile.record_batch.getChild('integers');
        const mask = new Bitmask(integers.length);
        for (let i = 0; i < integers.length; i += 1) {
          let int = integers.get(i);
          if (int % n === 0) {
            mask.set(i, true);
          }
        }
        return mask.to_arrow();
      }
    }
  }
  const done = new Set();
  function highlight(key) {
    const vals = functions[key](num_batches * 2 ** 16);
    const prime_ids = vals.map((d) => d.toString());

    if (!done.has(key)) {
      plot.select_data(
        {
          name: key,
          ids: prime_ids,
          field: '_id',          
        }
      )
    }

    if (key.slice(0, 7) == 'stable_') {
      done.add(key);
    }
    plot.plotAPI({
      duration: 1000,
      encoding: {
        foreground: {
          field: key,
          op: 'eq',
          a: 1,
        },
        size: {
          field: key,
          domain: [0, 1],
          range: [0.5, 5],
        },
      },
    });
  }
  document.getElementById('select-ids').addEventListener('click', () => {
    const ids = document
      .getElementById('input-ids')
      .value.split(' ')
      .filter((d) => d);
    const name = Math.random().toString(36);
    plot.select_and_plot({ name, ids, idField: '_id' }).then(selection => {
      window.selection = selection;
    })
  });

  document
    .getElementById('select-lots-of-ids')
    .addEventListener('click', () => {
      const ids = [];
      for (let i = 0; i < 1000; i += 1) {
        ids.push(Math.floor(Math.random() * 2 ** 16).toString());
      }
      const name = Math.random().toString(36);
      plot.select_and_plot({
        name,
        ids,
        idField: '_id',
      });
    });
  document
    .getElementById('prime')
    .addEventListener('click', () => highlight('prime'));
  document
    .getElementById('even')
    .addEventListener('click', () => highlight('even'));
  document
    .getElementById('even2')
    .addEventListener('click', () => highlight('stable_even'));
  document
    .getElementById('b')
    .addEventListener('click', () =>
      plot.plotAPI({ encoding: { foreground: null, size: {} } })
    );



  // function dictionaryFromArrays(indices, labels) {
  //   const labelsArrow = vectorFromArray(labels, new Utf8());
  //   let t;
  //   if (indices[Symbol.toStringTag] === `Int8Array`) {
  //     t = new Int8()
  //   } else if (indices[Symbol.toStringTag] === `Int16Array`) {
  //     t = new Int16()
  //   } else if (indices[Symbol.toStringTag] === `Int32Array`) {
  //     t = new Int32()
  //   } else {
  //     console.log(indices[Symbol.toStringTag])
  //     throw new Error("values must be an array of signed integers, 32 bit or smaller.")
  //   }
  //   console.log({ indices }, indices.length)
  //   const type = new Dictionary(labelsArrow.type, t, 0, false);
  //   const returnval = makeVector({
  //     type,
  //     length: indices.length,
  //     nullCount: 0,
  //     data: indices,
  //     dictionary: labelsArrow,
  //   });
  //   return returnval
  // }
</script>

<style>
  .buttons {
    position: fixed;
    top: 0;
    left: 0;
    padding: 20px;
    z-index: 199;
  }

  .tooltip {
    transform: translate(-50%, -100%);
  }
</style>